Amazon Elasticsearch ServiceをCloudFormationだけで作成する
まいど、大阪の市田です。 CloudFormationでAmazon Elasticsearch Service(以下 Elasticsearch Service)を作成する機会があったので、注意点を踏まえつつ紹介したいと思います。
CloudFormationで作成する時の注意点
早速ですが、Elasticsearch ServiceをCloudFormationで作成する際は、ドメインの作成時に「Elasticsearch Service」用のIAMロールとして「Service Linked Role」が必要になるという点に気をつける必要があります。
他のAWSサービスでも「Service Linked Role」が必要なものはありますが、多くの場合は明示的な指定がなくても自動的に作成されます。 しかし、Elasticsearch Serviceの場合は自動作成されないので何らかの方法で事前に作成しておく必要があります。(マネジメントコンソールで作成する場合は自動的に作成されます。)
Service Linked Roleについては、下記で紹介していますので参考にして頂ければと思います。
IAMのService-Linked RolesがCloudFormationに対応したので、とてもナイスなリリースということを詳しく書いてみた。
CloudFormationだけで作成する方法
色々と試してみて一番いいと思ったのは、「Service Linked Role」を作成するテンプレートはElasticsearch Service自体を作成するテンプレートとは別に分ける方法です。
「Service Linked Role」を作成するテンプレートは下記の通りシンプルです。
Resources: EsServiceLinkedRole: Type: "AWS::IAM::ServiceLinkedRole" Properties: AWSServiceName: es.amazonaws.com Description: "Service Linked Role for Amazon Elasticsearch Service"
なお、この「Service Linked Role」は一度作成すれば、複数のElasticsearchを作成する場合でも共通で利用されますが、すでに同じRoleがある場合に重複して作成しようとするとエラーになります。
次に、上記のCloudFormationが無事「CREATE_COMPLETE」になれば、 Elasticsearch Service を作成します。下記はTokyoリージョンで作成する場合の一例です。
Parameters: ESDomainName: Description: "Your Elasticsearch Domain Name" Type: String MinLength: 3 MaxLength: 28 AllowedPattern: "^[a-z0-9+-]*$" Resources: # VPC MyVPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.100.0.0/16 EnableDnsSupport: 'true' EnableDnsHostnames: 'true' InstanceTenancy: default Tags: - Key: Name Value: MyES-VPC # Subnet MyEsSubnet01: Type: AWS::EC2::Subnet Properties: VpcId: Ref: MyVPC CidrBlock: 10.100.0.0/24 MapPublicIpOnLaunch: true AvailabilityZone: ap-northeast-1a Tags: - Key: Name Value: MyES-Subnet-1a MyEsSubnet02: Type: AWS::EC2::Subnet Properties: VpcId: Ref: MyVPC CidrBlock: 10.100.1.0/24 MapPublicIpOnLaunch: true AvailabilityZone: ap-northeast-1c Tags: - Key: Name Value: MyES-Subnet-1c # Security Group # For Amazon Elasticsearch Service ElasticsearchSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: elasticsearch-sg VpcId: Ref: MyVPC Tags: - Key: Name Value: elasticsearch-sg SecurityGroupIngress: # for Elasticsearch Access - IpProtocol: tcp FromPort: '443' ToPort: '443' CidrIp: !GetAtt MyVPC.CidrBlock # Elasticsearch on VPC MyElasticsearch: Type: AWS::Elasticsearch::Domain Properties: AccessPolicies: Version: 2012-10-17 Statement: - Effect: Allow Principal: AWS: '*' Action: 'es:*' Resource: !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${ESDomainName}/* DomainName: !Ref ESDomainName EBSOptions: EBSEnabled: true VolumeSize: 10 VolumeType: gp2 ElasticsearchClusterConfig: InstanceCount: 2 InstanceType: t2.small.elasticsearch ZoneAwarenessEnabled: true ElasticsearchVersion: 6.2 SnapshotOptions: AutomatedSnapshotStartHour: 17 VPCOptions: SubnetIds: - !Ref MyEsSubnet01 - !Ref MyEsSubnet02 SecurityGroupIds: - !Ref ElasticsearchSecurityGroup
このように、ロールとElasticsearch Serviceを分けて作成するようにすれば、次回以降 Elasticsearch Service を作成する場合でも、都度「Service Linked Role」を作成する必要がありません。
CloudFormationが失敗する場合
さて、上記では 2回CloudFormationを実行する必要がありましたが、場合によっては 1回で終わらせたいという場合もあるかと思います。しかし上記のテンプレートを1つにまとめるとテンプレートの内容が正しくても失敗する場合があります。 (初めてElasitcsearch ServiceとService Linked Roleを作成する場合においてもです。)
パターンを全て確認した訳ではありませんが、「Service Linked Role の作成直後にElasticsearch Service を作成する」テンプレートの場合はCloudFormationが失敗します。
例えば、下記のように作成済みのVPC上に初めてElasticsearch Service を作成する場合です。初めて作る想定で「Service Linked Role」も作るようにしてみます。
下記のテンプレートでは、DependsOn
を使って「Service Linked Role」の作成完了後に、Elasticsearch Service を作成するようにしています。(20行目)
Parameters: ESDomainName: Description: "Your Elasticsearch Domain Name" Type: String MinLength: 3 MaxLength: 28 AllowedPattern: "^[a-z0-9+-]*$" Resources: # ES Service Linked Role EsServiceLinkedRole: Type: "AWS::IAM::ServiceLinkedRole" Properties: AWSServiceName: es.amazonaws.com Description: "This is a test" # Elasticsearch on VPC MyElasticsearch: Type: AWS::Elasticsearch::Domain DependsOn: EsServiceLinkedRole Properties: AccessPolicies: Version: 2012-10-17 Statement: - Effect: Allow Principal: AWS: '*' Action: 'es:*' Resource: !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${ESDomainName} DomainName: !Ref ESDomainName EBSOptions: EBSEnabled: true VolumeSize: 10 VolumeType: gp2 ElasticsearchClusterConfig: InstanceCount: 2 InstanceType: t2.small.elasticsearch ZoneAwarenessEnabled: true ElasticsearchVersion: 6.2 SnapshotOptions: AutomatedSnapshotStartHour: 17 VPCOptions: SubnetIds: - subnet-xxxxxxx - subnet-xxxxxxx SecurityGroupIds: - sg-xxxxxxxxx
しかし「Service Linked Role」の作成が「CREATE_COMPLETE」になってもElasticsearch Service から使用可能になるまでに時間が多少かかってしまうので、上記のような単一のテンプレート内容だと失敗します。
1回のCloudFormationで全部作成する方法
どうしても1回の作業だけで全て作成したい場合は、「Service Linked Role」の作成と「Elasticsearch Service」の作成タイミングの時間を明示的に空けるようにすればよいかと思います。
例えば、下記例のようにVPC作成前に「Service Linked Role」を作成してしまうといった方法です。 (この場合は、あえてDependsOnを下記のタイミングで書かなくても問題なく作成できました)
# VPC MyVPC: Type: "AWS::EC2::VPC" DependsOn: EsServiceLinkedRole Properties: (中略) # ES Service Linked Role EsServiceLinkedRole: Type: "AWS::IAM::ServiceLinkedRole" (中略) # Elasticsearch on VPC MyElasticsearch: Type: "AWS::Elasticsearch::Domain" Properties: AccessPolicies: Version: 2012-10-17 Statement: - Effect: Allow (以下略)
ただし、「Service Linked Role」は重複して作成しようとするとエラーになってしまう点などを考えると、1回のCloudFormation実行だけで作るのはおすすめしません。使い捨てで一度しか利用しないケースなど、適用できる場面は限られるかと思います。
ベストな作成方法
改めてベストな作成方法を考えてみると、CloudFormationで全部作成したい場合は、やはり冒頭で紹介したように「Service Linked Role」と「Elasticsearch Service」でそれぞれCloudFormationを分けて実行するのがよいでしょう。
「Service Linked Role」は初回の実行だけでよいので、大きな手間にもならないかと思います。
もちろん、CloudFormationにこだわらないということであれば、AWS CLIなどで実行してしまう方が楽かと思います。とはいえ、環境によってAWS CLIが利用できないといった理由があるかもしれませんので、状況によって使い分けていただければと思います。
最後に
今回は、実際の案件で「できる限りCloudFormationだけでAWS環境を作成したい」というリクエストを受けて、試行錯誤した内容をご紹介いたしました。
この記事が皆様のCloudFormationライフのお役に立てれば幸いです。 以上になります。